import { HyperFormula } from '../../../src'
import {CellError, ErrorType} from '../../../src/Cell'
import {Config} from '../../../src/Config'
import { SheetMapping } from '../../../src/DependencyGraph'
import {ErrorMessage} from '../../../src/error-message'
import { buildTranslationPackage } from '../../../src/i18n'
import { enUS } from '../../../src/i18n/languages'
import {AstNodeType, CellAddress, CellRangeAst, CellReferenceAst, ErrorAst} from '../../../src/parser'
import {adr} from '../testUtils'
import {buildEmptyParserWithCaching} from './common'

describe('Parser - OFFSET to reference translation', () => {
  it('OFFSET parsing into cell reference', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=OFFSET(F16, 0, 0)', adr('B3', 1)).ast as CellReferenceAst
    expect(ast.type).toBe(AstNodeType.CELL_REFERENCE)
    expect(ast.reference).toEqual(CellAddress.relative(4, 13))
  })

  it('OFFSET parsing into cell reference with row shift', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=OFFSET(F16, 1, 0)', adr('B3', 1)).ast as CellReferenceAst
    expect(ast.type).toBe(AstNodeType.CELL_REFERENCE)
    expect(ast.reference).toEqual(CellAddress.relative(4, 14))
  })

  it('OFFSET parsing into cell reference with negative row shift', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=OFFSET(C3, -1, 0)', adr('B2')).ast as CellReferenceAst
    expect(ast.type).toBe(AstNodeType.CELL_REFERENCE)
    expect(ast.reference).toEqual(CellAddress.relative(1, 0))
  })

  it('OFFSET parsing into cell reference with column shift', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=OFFSET(F16, 0, 1)', adr('B3', 1)).ast as CellReferenceAst
    expect(ast.type).toBe(AstNodeType.CELL_REFERENCE)
    expect(ast.reference).toEqual(CellAddress.relative(5, 13))
  })

  it('OFFSET parsing into cell reference with negative column shift', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=OFFSET(C3, 0, -1)', adr('B2')).ast as CellReferenceAst
    expect(ast.type).toBe(AstNodeType.CELL_REFERENCE)
    expect(ast.reference).toEqual(CellAddress.relative(0, 1))
  })

  it('OFFSET parsing into cell reference with some height', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=OFFSET(F16, 2, 0, 3)', adr('B3', 1)).ast as CellRangeAst
    expect(ast.type).toBe(AstNodeType.CELL_RANGE)
    expect(ast.start).toEqual(CellAddress.relative(4, 15))
    expect(ast.end).toEqual(CellAddress.relative(4, 17))
  })

  it('OFFSET parsing into cell reference with some width', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=OFFSET(F16, 0, 2, 1, 3)', adr('B3', 1)).ast as CellRangeAst
    expect(ast.type).toBe(AstNodeType.CELL_RANGE)
    expect(ast.start).toEqual(CellAddress.relative(6, 13))
    expect(ast.end).toEqual(CellAddress.relative(8, 13))
  })

  it('OFFSET parsing into cell range in different sheet', () => {
    const sheetMapping = new SheetMapping(buildTranslationPackage(enUS))
    sheetMapping.addSheet('Sheet1')
    sheetMapping.addSheet('Sheet2')
    const parser = buildEmptyParserWithCaching(new Config(), sheetMapping)

    const ast = parser.parse('=OFFSET(Sheet2!F16, 0, 2, 1, 3)', adr('B3', 0)).ast as CellRangeAst
    expect(ast.type).toBe(AstNodeType.CELL_RANGE)
    expect(ast.start).toEqual(CellAddress.relative(6, 13, 1))
    expect(ast.end).toEqual(CellAddress.relative(8, 13, 1))
  })

  it('OFFSET first argument need to be reference', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(42, 0, 0)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('First argument to OFFSET is not a reference')
  })

  it('OFFSET second argument need to be static number', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, C3, 0)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Second argument to OFFSET is not a static number')
  })

  it('OFFSET second argument need to be integer', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 1.3, 0)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Second argument to OFFSET is not a static number')
  })

  it('OFFSET third argument need to be static number', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, C3)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Third argument to OFFSET is not a static number')
  })

  it('OFFSET third argument need to be integer', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, 1.3)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Third argument to OFFSET is not a static number')
  })

  it('OFFSET fourth argument need to be static number', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, 0, B3)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Fourth argument to OFFSET is not a static number')
  })

  it('OFFSET fourth argument need to be static number bigger than 0', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, 0, 0)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Fourth argument to OFFSET is too small number')
  })

  it('OFFSET fourth argument need to be integer', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, 0, 1.3)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Fourth argument to OFFSET is not integer')
  })

  it('OFFSET fifth argument need to be static number', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, 0, 1, B3)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Fifth argument to OFFSET is not a static number')
  })

  it('OFFSET fifth argument need to be static number bigger than 0', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, 0, 1, 0)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Fifth argument to OFFSET is too small number')
  })

  it('OFFSET fifth argument need to be integer', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {errors} = parser.parse('=OFFSET(A1, 0, 0, 1, 1.3)', adr('A1'))
    expect(errors[0].type).toBe('StaticOffsetError')
    expect(errors[0].message).toBe('Fifth argument to OFFSET is not integer')
  })

  it('OFFSET resulting reference out of the sheet in top left row', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {ast, errors} = parser.parse('=OFFSET(A1, -1, 0)', adr('A1'))
    expect(errors.length).toBe(0)
    expect((ast as ErrorAst).error).toEqual(new CellError(ErrorType.REF, ErrorMessage.OutOfSheet))
  })

  it('OFFSET resulting reference out of the sheet in top left column', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const {ast, errors} = parser.parse('=OFFSET(A1, 0, -1)', adr('A1'))
    expect(errors.length).toBe(0)
    expect((ast as ErrorAst).error).toEqual(new CellError(ErrorType.REF, ErrorMessage.OutOfSheet))
  })

  it('OFFSET case insensitive', () => {
    const parser = buildEmptyParserWithCaching(new Config())

    const ast = parser.parse('=oFfSeT(F16, 0, 0)', adr('B3', 1)).ast as CellReferenceAst
    expect(ast.type).toBe(AstNodeType.CELL_REFERENCE)
    expect(ast.reference).toEqual(CellAddress.relative(4, 13))
  })

  it('parser returns address with a sheet reference', () => {
    const sheetMapping = new SheetMapping(buildTranslationPackage(enUS))
    sheetMapping.addSheet('Sheet1')
    sheetMapping.addSheet('Sheet2')
    const parser = buildEmptyParserWithCaching(new Config(), sheetMapping)

    const ast = parser.parse('=OFFSET(Sheet2!A1, 0, 0)', adr('A1', 0)).ast as CellReferenceAst
    expect(ast.type).toBe(AstNodeType.CELL_REFERENCE)
    expect(ast.reference).toEqual(CellAddress.relative(0, 0, 1))
  })

  it('function OFFSET can reference a different sheet', () => {
    const engine = HyperFormula.buildFromSheets({
      Sheet1: [['sheet1']],
      Sheet2: [['sheet2', '=OFFSET(Sheet1!A1, 0, 0)']],
    })

    expect(engine.getCellValue(adr('B1', engine.getSheetId('Sheet2')))).toEqual('sheet1')
  })
})
